package com.databasir.api.advice;


import com.databasir.common.DatabasirException;
import com.databasir.common.JsonData;
import com.databasir.common.exception.Forbidden;
import com.databasir.common.exception.InvalidTokenException;
import com.databasir.common.exception.SystemException;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;

import java.util.List;
import java.util.Set;

@RestControllerAdvice
@Slf4j
public class DatabasirExceptionAdvice extends ResponseEntityExceptionHandler {
    @ExceptionHandler({ConstraintViolationException.class})
    public ResponseEntity<Object> handleConstraintViolationException(
            ConstraintViolationException constraintViolationException, WebRequest request){
        String errorMsg="";
        String path=getPath(request);
        Set<ConstraintViolation<?>> violations = constraintViolationException.getConstraintViolations();
        for (ConstraintViolation<?> item : violations) {
            errorMsg = item.getMessage();
        log.warn("ConstraintViolationException, request: {}, exception: {}, invalid value: {}",
             path,errorMsg,item.getInvalidValue());
        break;
        }
     return handleNon200Response(errorMsg,HttpStatus.BAD_REQUEST,path);
    }

    @ExceptionHandler({InvalidTokenException.class})
    protected ResponseEntity<Object> handleInvalidTokenException(InvalidTokenException ex,WebRequest request){
        String path = getPath(request);
        log.warn("handle InvalidTokenException "+path+","+ex);
        JsonData<Object> data = JsonData.error(ex.getErrCode(), ex.getErrMessage());
        return handleNon200Response(ex.getMessage(), HttpStatus.UNAUTHORIZED,path,data);
    }
    @ExceptionHandler({IllegalArgumentException.class})
    protected ResponseEntity<Object> handleIllegalArgument(IllegalArgumentException ex,WebRequest request){
        String path = getPath(request);
        log.warn("handle illegalArgument "+path,ex);
        return handleNon200Response(ex.getMessage(),HttpStatus.BAD_REQUEST,path);
   }
   @ExceptionHandler({AccessDeniedException.class})
   protected ResponseEntity<Object> handleAccessDeniedException(AccessDeniedException ex,WebRequest request){
       String path = getPath(request);
       log.warn("AccessDeniedException, request: {}, exception: {}",path,ex.getMessage());
       return handleNon200Response(ex.getMessage(),HttpStatus.FORBIDDEN,path);

  }

  @ExceptionHandler({Forbidden.class})
  protected ResponseEntity<Object> handleForbiddenException(Forbidden ex,WebRequest request){
      String path = getPath(request);
      log.warn("Forbidden, request: {}, exception: {}",path,ex.getMessage());
      return handleNon200Response(ex.getMessage(),HttpStatus.FORBIDDEN,path);
  }

  @ExceptionHandler(value = DatabasirException.class)
  public ResponseEntity<Object> handleBusinessException(
   DatabasirException databasirException,WebRequest request ){
      String path = getPath(request);
      JsonData<Void> body = JsonData.error(databasirException.getErrCode(), databasirException.getErrMessage());
     if (databasirException.getCause()==null){
       log.warn("BusinessException request:{},exception:{}",path,databasirException.getErrMessage());
 }else{
         log.warn("BusinessException request:"+path,databasirException);
     }
    return  ResponseEntity.ok().body(body);
  }
   @ExceptionHandler({SystemException.class})
   public ResponseEntity<Object> handleSystemException(SystemException systemException,WebRequest request){
        String path = getPath(request);
        if (systemException.getCause()==null){
            log.warn("SystemException request: "+path+",exception"+systemException.getMessage()+",caused by"+systemException.getCause());
  }else {
            log.error("SystemException, request: " + path + ", exception: " + systemException.getMessage());
        }
       return handleNon200Response(systemException.getMessage(),HttpStatus.INTERNAL_SERVER_ERROR,path);
  }
  @ExceptionHandler({Exception.class})
  public ResponseEntity<Object> handleUnspecificException(Exception ex,WebRequest request){
       String path = getPath(request);
       String errorMsg = ex.getMessage();
       log.warn("Unspecific exception ,request"+path+", exception:"+errorMsg+":"+ex);
       return handleNon200Response(errorMsg,HttpStatus.INTERNAL_SERVER_ERROR,path);
 }
   @Override
   public ResponseEntity<Object> handleBindException(BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request){
        String errorMsg = buildMessages(ex.getBindingResult());
        log.warn("BindException,request:{} exception:{}",getPath(request),errorMsg);
       return handleOverriddenException(ex, headers, status, request, errorMsg);
 }
 @Override
 public  ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,HttpHeaders headers,HttpStatus status,WebRequest request){

       String errorMsg = buildMessages(ex.getBindingResult());
       log.warn("MethodArgumentNotValidException request: {} exception:{}",getPath(request),errorMsg);
       return handleOverriddenException(ex,headers,status,request,errorMsg);
 }

  @Override
  public ResponseEntity<Object> handleTypeMismatch(TypeMismatchException ex,HttpHeaders headers,HttpStatus status,WebRequest request){

        log.warn("TypeMisMatchException request:{},exception:{}",getPath(request),ex.getMessage());
        return handleOverriddenException(ex,headers,status,request,ex.getMessage());

  }






 private ResponseEntity<Object> handleOverriddenException(Exception ex,HttpHeaders headers,HttpStatus status,WebRequest request,String errorMsg){
  return handleExceptionInternal(ex,null,headers,status,request);
 }



   private String buildMessages(BindingResult result){

       StringBuilder resultBuilder = new StringBuilder();
       List<ObjectError> errors = result.getAllErrors();
       for (ObjectError error : errors) {
        if (error instanceof FieldError){
             FieldError fieldError = (FieldError) error;
             String fieldName = fieldError.getField();
             String fieldErrMsg = fieldError.getDefaultMessage();
             resultBuilder.append(fieldName).append(" ").append(fieldErrMsg);
    }
   }
       return resultBuilder.toString();

   }






    private ResponseEntity<Object> handleNon200Response(String errorMsg,HttpStatus httpStatus,String path,Object body){

     return ResponseEntity.status(httpStatus).body(body);
    }










    private ResponseEntity<Object> handleNon200Response(String errorMsg, HttpStatus httpStatus,String path){
        return ResponseEntity.status(httpStatus).body(null);
    }





    private String getPath(WebRequest request){
       String description = request.getDescription(false);
       return description.startsWith("uri=")?description.substring(4):description;
  }





}
